﻿/******************************************************************************
* SunnyUI.FrameDecoder 开源TCP、串口数据解码库。
* CopyRight (C) 2022-2023 ShenYongHua(沈永华).
* QQ群：56829229 QQ：17612584 EMail：SunnyUI@qq.com
*
* Blog:   https://www.cnblogs.com/yhuse
* Gitee:  https://gitee.com/yhuse/SunnyUI.FrameDecoder
*
* SunnyUI.FrameDecoder.dll can be used for free under the MIT license.
* If you use this code, please keep this note.
* 如果您使用此代码，请保留此说明。
******************************************************************************
* 文件名称: LengthValueFrameDecoder.cs
* 文件说明: 数据长度、数据 - 数据帧解码器
* 当前版本: V1.0
* 创建日期: 2022-11-01
*
* 2020-01-01: V1.0.0 增加文件说明
******************************************************************************/

using System;

namespace Sunny.FrameDecoder
{
    /// <summary>
    /// 数据长度、数据 - 数据帧解码器
    /// </summary>
    public class LengthValueFrameDecoder : BaseByteFrameDecoder
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="valueLengthType">数据长度类型</param>
        /// <param name="valueIsLittleEndian">数据长度字节顺序</param>
        /// <param name="isFullLength">数据长度是否包含数据长度的长度，false为仅数据长度</param>
        /// <param name="maxFrameLength">最大数据长度，仅判断数据长度</param>
        public LengthValueFrameDecoder(ValueLengthType valueLengthType, bool valueIsLittleEndian = true, bool isFullLength = false, int maxFrameLength = 0)
        {
            ValueLengthType = valueLengthType;
            IsFullLength = isFullLength;
            MaxFrameLength = maxFrameLength;
            ValueIsLittleEndian = valueIsLittleEndian;

            FrameExceptDataLength = valueLengthType.Length();
        }

        private bool ValueIsLittleEndian;

        /// <summary>
        /// 数据长度类型
        /// </summary>
        public ValueLengthType ValueLengthType { get; }

        /// <summary>
        /// 数据长度是否包含数据长度的长度，false为仅数据长度
        /// </summary>
        public bool IsFullLength { get; }

        /// <summary>
        /// 准备数据解码
        /// </summary>
        /// <param name="data">输入数据</param>
        /// <returns>判断此数据是否可以解码</returns>
        protected override bool PrepareDecode(ref byte[] data)
        {
            if (data == null)
            {
                DecoderError("The data to be decoded is null.", data);
                return false;
            }

            if (Cache.WrittenCount > 0)
            {
                return true;
            }

            if (data.Length < FrameExceptDataLength)
            {
                return false;
            }

            int len = data.AsSpan().Slice(0, ValueLengthType.Length()).GetLengthValue(ValueIsLittleEndian);
            int dataLen = IsFullLength ? len - ValueLengthType.Length() : len;

            if (dataLen < 0)
            {
                return false;
            }

            if (MinFrameLength > 0 && dataLen < MinFrameLength)
            {
                DecoderError("The data length is less than the minimum length", data);
                return false;
            }

            if (MaxFrameLength > 0 && MaxFrameLength > MinFrameLength && dataLen > MaxFrameLength)
            {
                DecoderError("The data length is greater than the maximum length.", data);
                return false;
            }

            return true;
        }

        /// <summary>
        /// 解码函数
        /// </summary>
        protected override void Decoding()
        {
            if (Cache.WrittenCount < FrameExceptDataLength) return;

            int len = Cache.WrittenSpan.GetLengthValue(0, ValueLengthType.Length(), ValueIsLittleEndian);
            int dataLen = IsFullLength ? len - FrameExceptDataLength : len;

            if (dataLen < 0)
            {
                Reset();
                return;
            }

            while (dataLen >= 0)
            {
                if (MinFrameLength > 0 && dataLen < MinFrameLength)
                {
                    DecoderError("The data length is less than the minimum length", Cache.WrittenSpan.ToArray());
                    Reset();
                    break;
                }

                if (MaxFrameLength > 0 && MaxFrameLength > MinFrameLength && dataLen > MaxFrameLength)
                {
                    DecoderError("The data length is greater than the maximum length.", Cache.WrittenSpan.ToArray());
                    Reset();
                    break;
                }

                int allLen = dataLen + FrameExceptDataLength;
                if (allLen <= Cache.WrittenCount)
                {
                    LastDecodedTime = DateTime.Now;
                    var e = new LengthValueFrameDataEventArgs(
                        this,
                        len,
                        Cache.WrittenSpan.Slice(ValueLengthType.Length(), dataLen).ToArray(),
                        Cache.WrittenSpan.Slice(0, allLen).ToArray());
                    DoDecoder(this, e);
                }
                else
                {
                    break;
                }

                Cache.Remove(allLen);
                if (Cache.WrittenCount < FrameExceptDataLength) return;

                len = Cache.WrittenSpan.GetLengthValue(0, ValueLengthType.Length(), ValueIsLittleEndian);
                dataLen = IsFullLength ? len - FrameExceptDataLength : len;
            }
        }

        /// <summary>
        /// 根据数据创建完整帧数据
        /// </summary>
        /// <param name="data">数据</param>
        /// <returns>完整帧数据</returns>
        public byte[] CreateFrame(byte[] data)
        {
            byte[] frame = new byte[FrameExceptDataLength + data.Length];
            switch (ValueLengthType)
            {
                case ValueLengthType.Byte:
                    ByteHelper.TryWriteByte(frame, 0, (byte)(IsFullLength ? data.Length + FrameExceptDataLength : data.Length));
                    break;
                case ValueLengthType.UShort:
                    ByteHelper.TryWriteUShort(frame, 0, (ushort)(IsFullLength ? data.Length + FrameExceptDataLength : data.Length), ValueIsLittleEndian);
                    break;
                case ValueLengthType.UInt:
                    ByteHelper.TryWriteUInt(frame, 0, (uint)(IsFullLength ? data.Length + FrameExceptDataLength : data.Length), ValueIsLittleEndian);
                    break;
            }

            Array.Copy(data, 0, frame, FrameExceptDataLength, data.Length);
            return frame;
        }
    }
}
